/*
 * Copyright (C) 2013 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.wdullaer.materialdatetimepicker;

import com.wdullaer.materialdatetimepicker.common.AnimatorValue2;
import com.wdullaer.materialdatetimepicker.common.ResourceUtils;
import ohos.agp.animation.Animator;
import ohos.agp.animation.AnimatorValue;
import ohos.agp.colors.HsvColor;
import ohos.agp.components.AttrHelper;
import ohos.agp.components.Component;
import ohos.agp.text.Font;
import ohos.app.Context;
import ohos.app.Environment;
import ohos.global.resource.RawFileEntry;
import ohos.global.resource.Resource;
import ohos.global.resource.ResourceManager;
import ohos.hiviewdfx.HiLog;
import ohos.hiviewdfx.HiLogLabel;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.Method;
import java.util.Calendar;

/**
 * Utility helper functions for time and date pickers.
 */
@SuppressWarnings("WeakerAccess")
public class Utils {

    //public static final int MONDAY_BEFORE_JULIAN_EPOCH = Time.EPOCH_JULIAN_DAY - 3;
    public static final int PULSE_ANIMATOR_DURATION = 544;

    // Alpha level for time picker selection.
    public static final int SELECTED_ALPHA = 255;
    public static final int SELECTED_ALPHA_THEME_DARK = 255;
    // Alpha level for fully opaque.
    public static final int FULL_ALPHA = 255;

    /**
     * Try to speak the specified text, for accessibility. Only available on JB or later.
     *
     * @param component
     * @param text text Text to announce.
     */
    public static void tryAccessibilityAnnounce(Component component, CharSequence text) {
        if (component != null && text != null) {
            component.announceAccessibility(String.valueOf(text));
        }
    }

    public static AnimatorValue getKeyFrameAnimator(Component labelToAnimate, float decreaseRatio,
                                                 float increaseRatio) {
        AnimatorValue animator = new AnimatorValue();
        animator.setCurveType(Animator.CurveType.LINEAR);
        animator.setValueUpdateListener(new AnimatorValue.ValueUpdateListener() {
            @Override
            public void onUpdate(AnimatorValue animatorValue, float v) {
                float value = 1f;
                final float x[] = {0f, 0.275f, 0.69f, 1f};
                final float y[] = {1f, decreaseRatio, increaseRatio, 1f};
                for (int i = 0; i < x.length; i++) {
                    if (i > 0 && v <= x[i]) {
                        value = y[i - 1] + (v - x[i - 1]) * (y[i] - y[i - 1]) / (x[i] - x[i - 1]);
                        break;
                    }
                }
                labelToAnimate.setScale(value, value);
            }
        });
        animator.setDuration(PULSE_ANIMATOR_DURATION);
        return animator;
    }

    /**
     * Render an animator to pulsate a view in place.
     * @param labelToAnimate the view to pulsate.
     * @param decreaseRatio
     * @param increaseRatio
     * @return The animator object. Use .start() to begin.
     */
    public static AnimatorValue getPulseAnimator(Component labelToAnimate, float decreaseRatio,
                                                 float increaseRatio) {
        AnimatorValue animator = new AnimatorValue();
        animator.setCurveType(Animator.CurveType.LINEAR);
        animator.setValueUpdateListener(new AnimatorValue.ValueUpdateListener() {
            @Override
            public void onUpdate(AnimatorValue animatorValue, float v) {
                float value = 1f;
                // (y-y0)/(x-x0) = (y1-y0)/(x1-x0)  -> y = y0 + (x-x0)*(y1-y0)/(x-x0)
                final float x[] = {0f, 0.275f, 0.69f, 1f};
                final float y[] = {1f, decreaseRatio, increaseRatio, 1f};
                for (int i = 0; i < x.length; i++) {
                    if (i > 0 && v <= x[i]) {
                        value = y[i - 1] + (v - x[i - 1]) * (y[i] - y[i - 1]) / (x[i] - x[i - 1]);
                        break;
                    }
                }
                labelToAnimate.setScale(value, value);
            }
        });
        animator.setDuration(PULSE_ANIMATOR_DURATION);
        return animator;
    }

    public static class Keyframe {
        float progress;
        float value;

        public Keyframe(float progress, float value) {
            this.progress = progress;
            this.value = value;
        }

        public static Keyframe ofFloat(float progress, float value) {
            return new Keyframe(progress, value);
        }
    }

    public static class PropertyValuesHolder{
        String property;
        Keyframe[] frames;

        public PropertyValuesHolder(String property, Keyframe... frames) {
            this.property = property;
            this.frames = frames;
        }
        public static PropertyValuesHolder ofKeyframe(String property, Keyframe... frames) {
            return new PropertyValuesHolder(property, frames);
        }
    }

    public static AnimatorValue getPropertyAnimator(Component component, String property, Keyframe... frames) {
        return getPropertyAnimator(component, new PropertyValuesHolder(property, frames));
    }

    public static AnimatorValue2 getPropertyAnimator(Component component, PropertyValuesHolder... holders) {
        AnimatorValue2 animator = new AnimatorValue2();
        animator.setCurveType(Animator.CurveType.LINEAR);
        animator.addUpdateListener(new AnimatorValue.ValueUpdateListener() {
            @Override
            public void onUpdate(AnimatorValue animatorValue, float v) {
                for(PropertyValuesHolder holder : holders) {
                    float value = 1f;
                    // (y-y0)/(x-x0) = (y1-y0)/(x1-x0)  -> y = y0 + (x-x0)*(y1-y0)/(x1-x0)
                    for (int i = 0; i < holder.frames.length; i++) {
                        float x1 = holder.frames[i].progress;
                        float y1 = holder.frames[i].value;
                        if (i > 0 && v <= x1) {
                            float x0 =  holder.frames[i-1].progress;
                            float y0 = holder.frames[i-1].value;
                            value = y0 + (v - x0) * (y1 - y0) / (x1 - x0);
                            break;
                        }
                    }
                    String methodName = "set"+holder.property.toUpperCase().charAt(0)+holder.property.substring(1);
                    try {
                        Method method = component.getClass().getMethod(methodName, float.class);
                        Utils.log("onUpdate name:"+((AnimatorValue2)animatorValue).getName()
                                +", property:"+holder.property
                                + ", method:"+method.getName()
                                +", value("+v+" --> "+value+")");
                        method.invoke(component, value);
                    } catch (Exception e) {
                        Utils.log("AnimatorValue2 invoke method exception: "+e);
                    }
                }
            }
        });
        return animator;
    }

    private static String TAG = "MonthFragment";
    private static HiLogLabel LABEL = new HiLogLabel(HiLog.LOG_APP, 1234567, TAG);
    public static void log(String format, Object... args) {
        HiLog.warn(LABEL, format, args);
    }

    /**
     * 获取canlendar
     *
     * @param calendar 日历
     * @return {@link String}
     */
    public static String getCanlendarStr(Calendar calendar){
        return "year:"+calendar.get(Calendar.YEAR)+", month:"+calendar.get(Calendar.MONTH)+", day:"+calendar.get(Calendar.DAY_OF_MONTH);
    }

    /**
     * Convert vp to Pixel
     *
     * @param vp vp
     * @param context 上下文
     * @return int
     */
    @SuppressWarnings("unused")
    public static int vpToPx(float vp, Context context) {
        return AttrHelper.vp2px(vp, context);
    }

    public static int darkenColor(int color) {
        HsvColor hsvColor = HsvColor.toHSV(color);
        hsvColor.setValue(hsvColor.getValue() * 0.8f); // value component
        return HsvColor.toColor(255, hsvColor.getHue(), hsvColor.getSaturation(), hsvColor.getValue());
    }

    /**
     * Gets the colorAccent from the current context, if possible/available
     * @param context The context to use as reference for the color
     * @return the accent color of the current context
     */
    public static int getAccentColorFromThemeIfAvailable(Context context) {
        return ResourceUtils.getColor(context, ResourceTable.Color_mdtp_accent_color).getValue();
    }

    /**
     * Gets dialog type (Light/Dark) from current theme
     * @param context The context to use as reference for the boolean
     * @param current Default value to return if cannot resolve the attribute
     * @return true if dark mode, false if light.
     */
    public static boolean isDarkTheme(Context context, boolean current) {
//        return resolveBoolean(context, R.attr.mdtp_theme_dark, current);
        return false;
    }

    /**
     * Gets the required boolean value from the current context, if possible/available
     * @param context The context to use as reference for the boolean
     * @param attr Attribute id to resolve
     * @param fallback Default value to return if no value is specified in theme
     * @return the boolean value from current theme
     */
//    private static boolean resolveBoolean(Context context, /*@AttrRes */int attr, boolean fallback) {
//        TypedArray a = context.getTheme().obtainStyledAttributes(new int[]{attr});
//        try {
//            return a.getBoolean(0, fallback);
//        } finally {
//            a.recycle();
//        }
//    }

    /**
     * Trims off all time information, effectively setting it to midnight
     * Makes it easier to compare at just the day level
     *
     * @param calendar The Calendar object to trim
     * @return The trimmed Calendar object
     */
    public static Calendar trimToMidnight(Calendar calendar) {
        calendar.set(Calendar.HOUR_OF_DAY, 0);
        calendar.set(Calendar.MINUTE, 0);
        calendar.set(Calendar.SECOND, 0);
        calendar.set(Calendar.MILLISECOND, 0);
        return calendar;
    }

    public static Font getFont(Context context,String fontPath) throws IOException {
        StringBuffer fileName = new StringBuffer( fontPath.substring(fontPath.lastIndexOf("/")+1));
        File file = new File(context.getExternalFilesDir(Environment.DIRECTORY_PICTURES), fileName.toString());
        OutputStream outputStream = null;
        ResourceManager resManager = context.getResourceManager();
        RawFileEntry rawFileEntry = resManager.getRawFileEntry(fontPath);
        Resource resource = null;
        try {
            resource = rawFileEntry.openRawFile();
            outputStream = new FileOutputStream(file);
            int index;
            byte[] bytes = new byte[1024];
            if (resource != null) {
                while ((index = resource.read(bytes)) != -1) {
                    outputStream.write(bytes, 0, index);
                    outputStream.flush();
                }
                Font.Builder builder = new Font.Builder(file);
                Font font = builder.build();
               return font;
            }
        }  catch (FileNotFoundException e) {
        } finally {
            if (resource!=null){
                resource.close();
                outputStream.close();
            }
        }
        return null;
    }
}
